package org.progin.cloudfolder.service;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.progin.cloudfolder.DB;
import org.progin.cloudfolder.msg.DeletePayload;
import org.progin.cloudfolder.msg.ErrorPayload;
import org.progin.cloudfolder.msg.GotoRevPayload;
import org.progin.cloudfolder.msg.LoginPayload;
import org.progin.cloudfolder.msg.Msg;
import org.progin.cloudfolder.msg.Payload;
import org.progin.cloudfolder.msg.RevInfoPayload;
import org.progin.cloudfolder.msg.SavePayload;
import org.progin.cloudfolder.msg.SavePayloadAdapter;

public class CloudFolderService implements SavePayloadAdapter, Runnable {
	//Connection attribute
    Socket clientsock;
    DataInputStream dis;
    DataOutputStream dos;
    boolean run;
    Msg msg;
    
    //Database attribute
    DB 	m_Database;
    int m_UserID;
    //int m_UserRevision;
    
    public CloudFolderService(Socket s) throws IOException {
    	//Initialize connection
        clientsock = s;
        dis = new DataInputStream(clientsock.getInputStream());
        dos = new DataOutputStream(clientsock.getOutputStream());
        msg = new Msg();
        run = true;
        
        //Initialize database handler
        String m_Url = "jdbc:mysql://localhost/cloudfolder";
        String m_Username = "root";
        String m_Password = "";
        
        m_Database = new DB(m_Url, m_Username, m_Password);
        m_UserID = 0;
        //m_UserRevision = 0;
        
        System.out.println("Starting connection with " + s.getInetAddress());
    }

    public void run() {
        try {
            //Get Username Password, authenticate
            boolean auth = false;
            msg.initRead(dis);
            if(msg.isPayloadTypeEquals(Payload.LOGIN)) {
                    LoginPayload p = new LoginPayload();
                    msg.read(p, dis);
                    auth = authenticate(p);
            }
            
            //If auth failed, exit
            if(!auth) {
                    //Send error
                    ErrorPayload p = new ErrorPayload(ErrorPayload.AUTHFAILED);
                    msg.write(p, dos);

                    //Fail the loop
                    run = false;
            } else {
            	//Show user id
                System.out.println("User id is " + m_UserID);
                
                //Get current revision
                //m_UserRevision = getCurrentRevision();
                
                //Send OK
                msg.write(Payload.OK, dos);
            }
            
            //Looping for another message
            while(run) {
                    //Get Msg
                    msg.initRead(dis);

                    //Check each msg
                    if(msg.isPayloadTypeEquals(Payload.GETREV)) {
						System.out.println("GETREV");
                            handleGetRev();
                    } else if(msg.isPayloadTypeEquals(Payload.GOTOREV)) {
						System.out.println("GOTOREV");
                            GotoRevPayload p = new GotoRevPayload();
                            msg.read(p, dis);
                            handleGoToRev(p);
                    } else if(msg.isPayloadTypeEquals(Payload.SAVE)) {
						System.out.println("SAVE");
                            SavePayload p = new SavePayload(this);
                            msg.read(p, dis);
                            handleSave(p);
                    } else if(msg.isPayloadTypeEquals(Payload.DELETE)) {
						System.out.println("DELETE");
                            DeletePayload p = new DeletePayload();
                            msg.read(p, dis);
                            handleDelete(p);
                    } /*else if(msg.isPayloadTypeEquals(Payload.LOGOUT)) {
						System.out.println("LOGOUT");
                            run = false;
                    }*/ else {
						System.out.println("INVALIDMSG");
                            //Send error
                            ErrorPayload p = new ErrorPayload(ErrorPayload.INVALIDMSG);
                            msg.write(p, dos);
                    }
            }
        } catch (Exception e) {
                e.printStackTrace();
        } finally {
                //Loop done, clean up
                try {
                        if(dis != null) dis.close();
                        if(dos != null) dos.close();
                        if(clientsock != null) clientsock.close();
                        if (m_Database != null) m_Database.Dispose();
                        
                        System.out.println("Connection with " + clientsock.getInetAddress() + " closed.");
						System.out.println();
                } catch (Exception e) {
                        e.printStackTrace();
                }
        }
    }

    private void handleGetRev() throws IOException {
        //Get last rev from DB
        int rev = getCurrentRevision();
        
		System.out.println("handlegetrev " + clientsock.getPort() + " = " + rev);

		//Send rev info
        msg.write(new RevInfoPayload(rev), dos);
    }

    private void handleSave(SavePayload p) throws IOException {
    	//Increment revision
    	int newRevision = getCurrentRevision() + 1;
    	
        //Check curfiles table
        boolean fileExists = false;
        
        //Fetch
        String query = "SELECT * FROM `repo` WHERE `user_id`='" + m_UserID + "' AND `filename`='" + _realFileName + "'";
        ResultSet m_File = m_Database.executeQuery(query);
        
        //Get result from query
        try {
			m_File.next();
	        if (m_File.getRow()==1) fileExists = true;
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
        //If the designated filename existed
        if(fileExists) {
            //Update history table with MODIFY
        	query = "INSERT INTO `history` (`user_id`,`rev_n`,`action`,`filename`) VALUES ('" + m_UserID + "','" + newRevision + "','1','" + _realFileName + "')";
        	m_Database.executeUpdate(query);
        	
            //Update curfiles table with the new revision from history table
        	query = "UPDATE `repo` SET `rev_n`='" + newRevision + "' WHERE `user_id`='" + m_UserID + "' AND `filename`='" + _realFileName + "'";
        	m_Database.executeUpdate(query);
        } else {
            //Update history table with ADD
        	query = "INSERT INTO `history` (`user_id`,`rev_n`,`action`,`filename`) VALUES ('" + m_UserID + "','" + newRevision + "','0','" + _realFileName + "')";
        	m_Database.executeUpdate(query);
        	
            //Update curfiles table with the new file
        	query = "INSERT INTO `repo` (`user_id`,`rev_n`,`filename`) VALUES ('" + m_UserID + "','" + newRevision + "','" + _realFileName + "')";
        	m_Database.executeUpdate(query);
        }
        
        //Get rev number
        int rev = getCurrentRevision();

		System.out.println("rev = " + rev);
		
        //Rename the saved file to the real name
        File newFile = new File(getFileName(_userRepo, _realFileName, rev));
        
        System.out.println("New file path is " + newFile.getAbsolutePath());
        
        if(_curFile.renameTo(newFile)) {
            //Send Revision information indicating operation succeeded
			System.out.println("send rev = " + rev);
        	RevInfoPayload r = new RevInfoPayload(rev);
            msg.write(r, dos);
        } else {
            //Send error indicating failure
			System.out.println("ERROR rev");
        	ErrorPayload m_Error = new ErrorPayload(ErrorPayload.INVALIDMSG);
        	msg.write(m_Error, dos);
        }
    }

    private void handleGoToRev(GotoRevPayload p) throws IOException {
        //Initialize handler
    	HashMap<String, Integer> 	m_SaveHandler = new HashMap<String, Integer>();
    	ArrayList<String>			m_DeleteHandler = new ArrayList<String>();
    	
    	//Check revision
        if(p.from < p.to) {
            //Client wants to update his repository
        	String query = "SELECT * FROM `history` WHERE `user_id`='" + m_UserID + "' AND `rev_n`>'" + p.from + "' AND `rev_n`<='" + p.to + "' ORDER BY `rev_n` ASC";
        	ResultSet m_Temp = m_Database.executeQuery(query);
        	
        	//Iterate result
        	try {
	        	while(m_Temp.next()) {
	        		//If add or modify
	        		if(m_Temp.getInt("action")!=2) {
						System.out.println("Add to SAVE handler " + m_Temp.getString("filename"));
	        			m_SaveHandler.put(m_Temp.getString("filename"), m_Temp.getInt("rev_n"));
	        		} else {
	        			//if delete, check save handler first
	        			if (m_SaveHandler.containsKey(m_Temp.getString("filename"))) {
							System.out.println("Remove from SAVE handler " + m_Temp.getString("filename"));
	        				m_SaveHandler.remove(m_Temp.getString("filename"));
	        			} else {
							System.out.println("Add to DELETE handler " + m_Temp.getString("filename"));
	        				m_DeleteHandler.add(m_Temp.getString("filename"));
	        			}
	        		}
	        	}
        	} catch (SQLException e) {
        		e.printStackTrace();
        	}
        } else {
            //Client wants to revert to previous revisions
        	String query = "SELECT * FROM `history` WHERE `user_id`='" + m_UserID + "' AND `rev_n`>'" + p.to + "' AND `rev_n`<='" + p.from + "' ORDER BY `rev_n` DESC";
        	ResultSet m_Temp = m_Database.executeQuery(query);
        	
        	//Create temporary result set
        	String tempQuery = "SELECT * FROM `history` WHERE `user_id`='" + m_UserID + "' AND `rev_n`<='" + p.from + "' ORDER BY `rev_n` DESC";
        	ResultSet m_TempResult = m_Database.executeQuery(tempQuery);
        	
        	//Iterate result
        	try {
            	m_TempResult.next();
            	
            	//Loop until all was processed
	        	while(m_Temp.next()) {
	        		System.out.println("Now processing file " + m_Temp.getString("filename") + " in revision " + m_Temp.getInt("rev_n"));
	        		
	        		//if command is add
	        		if (m_Temp.getInt("action")==0) {
	        			//delete file
						System.out.println("Add to DELETE handler " + m_Temp.getString("filename"));
	        			if(m_SaveHandler.remove(m_Temp.getString("filename")) == null)
							m_DeleteHandler.add(m_Temp.getString("filename"));
	        		} else {
	        			//if delete or modify, iterate to find the latest modify or add
	        			while (m_Temp.getRow()>m_TempResult.getRow()) m_TempResult.next();
	        			Boolean found = false;
	        			
	        			while(!found&&m_TempResult.next()) {
	        				found = (m_TempResult.getString("filename").equals(m_Temp.getString("filename"))&&
	        						((m_Temp.getInt("action")==2)||(m_Temp.getInt("action")==1)));
	        			}
	        			
	        			//send if found
	        			if (found) {
							if(!m_DeleteHandler.remove(m_Temp.getString("filename"))) {
								System.out.println("Add to SAVE handler " + m_TempResult.getString("filename"));
								m_SaveHandler.put(m_TempResult.getString("filename"), m_TempResult.getInt("rev_n"));
							}
	        			}

                        //reset
                        m_TempResult.first();
	        		}
	        	}

				System.out.println("Done looping");
        	} catch (SQLException e) {
        		e.printStackTrace();
        	}
        }

		//Send delete file message for each files that client should delete
        for (int i=0;i<m_DeleteHandler.size();i++) {
			System.out.println("DELETE " + m_DeleteHandler.get(i));
        	DeletePayload d = new DeletePayload();
        	d.filename = m_DeleteHandler.get(i);
        	msg.write(d, dos);
        }

        //Send save file message for each files that client should download
        Set entries = m_SaveHandler.entrySet();
        Iterator itr = entries.iterator();

        while (itr.hasNext()) {
        	//Get paired elements
        	Map.Entry entry = (Map.Entry) itr.next();

			System.out.println("SAVE " + entry.getKey().toString());
        	//Create payload
        	SavePayload s = new SavePayload(this);
        	s.filename = entry.getKey().toString();
        	_tempRev = Integer.parseInt(entry.getValue().toString());

        	//Send
        	msg.write(s, dos);
        }

		System.out.println("mestinya ngirim OK");
        //Send OK indicating end of changes
        msg.write(Payload.OK, dos);
		System.out.println("OK terkirim");
    }

    private void handleDelete(DeletePayload p) throws IOException {
    	//Get temporary filename
    	String m_Temp = p.filename.replace('\\', '/');
    	
        //Update history table with DELETE command
    	int newRevision = getCurrentRevision() + 1;
    	String query = "INSERT INTO `history` (`user_id`,`rev_n`,`action`,`filename`) VALUES ('" + m_UserID + "','" + newRevision + "','2','" + m_Temp + "')" ;
    	m_Database.executeUpdate(query);
    	
        //Delete curfiles table that have appropriate filename
    	query = "DELETE FROM `repo` WHERE `user_id`='" + m_UserID + "' AND filename='" + m_Temp + "'";
    	m_Database.executeUpdate(query);
    	
    	//Update current revision
    	//m_UserRevision = getCurrentRevision();
    	
        //Send Revision information indicating operation succeeded
    	RevInfoPayload r = new RevInfoPayload(newRevision);
        msg.write(r, dos);
    }

    private boolean authenticate(LoginPayload p) {
		System.out.println("\t" + p.username + "#" + p.password);

    	//Initialize
    	Boolean m_Auth = false;
        //Send username password query
    	String m_Query = "SELECT * FROM `user` WHERE `uname` = '" + p.username +"' AND `passw` = '" + p.password + "'";
    	ResultSet m_Result = m_Database.executeQuery(m_Query);
    	
    	//Check result
    	try {
    		//Get first data
			m_Result.next();
	    	if (m_Result.getRow()==1) {
	    		//Assert login data
	    		m_UserID = m_Result.getInt("id");
	    		
	    		//Initialize repo folder
	    		_userRepo = Integer.toString(m_UserID);
	    		File repoFolder = new File("repo/" + _userRepo);
	    		if (!repoFolder.exists()) {repoFolder.mkdirs();}
	    		
	    		//Set flag
	    		m_Auth = true;
	    		}
		} catch (SQLException e) {
			e.printStackTrace();
		}
    	
        return m_Auth;
    }
    
    private int getCurrentRevision() {
        String m_Query = "SELECT * FROM `history` WHERE `user_id` = '" + m_UserID + "' ORDER BY `id` DESC";
        ResultSet m_Result = m_Database.executeQuery(m_Query);
    	int m_CurrentRev = 0;
    	
    	//Check result
    	try {
       		//Get first data
			m_Result.next();
	    	if (m_Result.getRow()>=1) {
	    		//Assert login data
				m_CurrentRev = m_Result.getInt("rev_n");
	    		}
		} catch (SQLException e) {
			e.printStackTrace();
		}

		//Return
		return m_CurrentRev;
    }


    /*********** ADAPTER INTERFACE *************************/

    private File    _curFile;
    private String  _realFileName;
    private String  _userRepo;
    private int 	_tempRev;

    public void initWrite(String filename) {
        //The file will be read and sent to client
        //Get the file
        _curFile = new File(getFileName(_userRepo, filename, _tempRev));

        //If file is not existed, error
        if(!_curFile.exists()) {
            System.out.println("File not exist.");
        }
    }

    public void initRead(String filename) {
        try {
            //New file will be added to server
            //Save the real file name
            _realFileName = filename;

            System.out.println("File real name is " + _realFileName);
            
            String test = _realFileName.replace('\\', '/');
            _realFileName = test;
            
            //create directory first
            int lastIndex = _realFileName.lastIndexOf("/");
            if (lastIndex!=0) {
	            String dir = _realFileName.substring(0, lastIndex);
	            new File("repo/" + _userRepo + "/" + dir).mkdirs();
	            
	            System.out.println("File directory is " + dir);
            }
            
            //Generate file with random, temporary filename
            do {
                _curFile = new File(getFileName(_userRepo, filename + Math.random(), 0));
            } while (!_curFile.createNewFile());
            
            System.out.println("Current file path is " + _curFile.getAbsolutePath());
            
        } catch (IOException ex) {
            Logger.getLogger(CloudFolderService.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public File getFile() {
        return _curFile;
    }

    public static String getFileName(String userRepo, String filename, int revision) {
        return "repo/" + userRepo + "/" + filename + "_r" + revision;
    }
}
